package com.sky.testmodule.controller.redis;

import com.alibaba.fastjson.JSON;
import com.sky.testmodule.constant.TestConstant;
import com.sky.testmodule.entity.TestEntity;
import com.sky.testmodule.util.HttpRequestUtil;
import org.redisson.Redisson;
import org.redisson.api.RLock;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.ValueOperations;
import org.springframework.data.redis.serializer.StringRedisSerializer;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import javax.servlet.http.HttpServletRequest;
import java.util.ArrayList;
import java.util.List;
import java.util.UUID;
import java.util.concurrent.TimeUnit;

/**
 * @ClassName StringController
 * @Description 缓存redis字符串控制层
 * @Author zhiwei liao
 * @Date 2021/8/818:53
 * @Version 1.0
 **/
@RequestMapping("/string")
@RestController
public class StringController {

    private final static org.slf4j.Logger logger = LoggerFactory.getLogger(StringController.class);

    @Autowired
    RedisTemplate redisTemplate;

    @Autowired
    private Redisson redisson;

    /**
     * 单值缓存
     * @return
     */
    @GetMapping("/singleValueCache")
    public Object singleValueCache(){
        //SET  key  value
        redisTemplate.opsForValue().set(TestConstant.KEY_TEST,String.valueOf(System.currentTimeMillis()));
        //GET  key
        return redisTemplate.opsForValue().get(TestConstant.KEY_TEST);
    }

    /**
     * 对象缓存
     * @return
     */
    @GetMapping("/objectCache")
    public Object objectCache(){
        TestEntity testEntity = new TestEntity();
        testEntity.setKey(TestConstant.OBJECT_CACHE_KEY);
        testEntity.setValue(TestConstant.OBJECT_CACHE_VALUE);
        List<String> list = new ArrayList<>();
        list.add(TestConstant.OBJECT_CACHE_VALUE);
        testEntity.setLists(list);
        String objectCacheString = JSON.toJSONString(testEntity);
        redisTemplate.opsForValue().set(TestConstant.OBJECT_CACHE_KEY,objectCacheString);
        return redisTemplate.opsForValue().get(TestConstant.OBJECT_CACHE_KEY);
    }


    /**
     * 分布式锁底层实现原理
     * @return
     */
    @GetMapping("distributedLock")
    public Object distributedLock(){
        String lockKey = "distributedLockKey";
        //给每个线程都设置一个唯一标识，避免出现程序执行的时间超过设置的过期时间，导致其他线程删除了自己的锁
        String clientId = UUID.randomUUID().toString();
        try {
            //添加过期时间保证线程运行到一半的时候，程序崩了，导致缓存中的key一直存在
            Boolean result = redisTemplate.opsForValue().setIfAbsent(lockKey, clientId, 30, TimeUnit.SECONDS);
            if (!result) {
                return "error_code";
            }
            // Todo 实现业务逻辑
            redisTemplate.opsForValue().set(TestConstant.KEY_TEST,String.valueOf(System.currentTimeMillis()));
        } finally {
            //判断是不是自己的线程删除自己的锁
            if (clientId.equals(redisTemplate.opsForValue().get(lockKey))) {
                redisTemplate.delete(lockKey);
            }
        }
        //Todo 当前代码未实现锁续命功能，锁续命功能：由于程序执行时间可能超过设置缓存中锁的过期时间，导致后续一部分业务未执行，一直被其他线程抢占，需要对锁进行续命,但是由于续命的时间不好确定，这个时候就需要单独开启子线程，定时任务不断的去判断当前锁，还在不在，如果不在了，说明业务已经执行完成了，如果还在，重新设置过期时间,一般而言，定时任务的时间为缓存设置的过期时间三分之一就可以了。
        return "end";
    }


    /**
     * 基于redisson实现高并发分布式锁
     * @return
     */
    @GetMapping("highConcurrencyDistributedLock")
    public Object highConcurrencyDistributedLock(){
        RLock redissonLock = redisson.getLock(TestConstant.HIGH_CONCURRENCY_DISTRIBUTED_LOCK);
        try {
            //加锁，实现了key设置，过期时间，锁续命功能
            redissonLock.lock();  //redisTemplate.opsForValue().setIfAbsent(lockKey, clientId, 30, TimeUnit.SECONDS);
            // Todo 实现业务逻辑
            redisTemplate.opsForValue().set(TestConstant.KEY_TEST,String.valueOf(System.currentTimeMillis()));
        } finally {
            //释放锁
            redissonLock.unlock();
        }
        return redisTemplate.opsForValue().get(TestConstant.KEY_TEST);
    }

    /**
     * @Description 实现计数器，每执行一次加一;可实现ip访问次数限制
     * @MethodReturnType java.lang.Object
     * @Author zhiwei Liao
     * @Date 2021/8/10 10:27
     **/
    @GetMapping("/counter")
    public Object counter(){
        // 接收到请求，记录请求内容
        ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        HttpServletRequest request = attributes.getRequest();
        String ip = HttpRequestUtil.getIpAddress(request);
        String url = request.getRequestURL().toString();
        String key = "req_limit_".concat(url).concat(ip);
        //incr  key  value  实现计数器，每执行一次加一;可实现ip访问次数限制 increment是float类型
        ValueOperations<String, String> operations = redisTemplate.opsForValue();
        //GenericToStringSerializer、StringRedisSerializer将字符串的值直接转为字节数组
        redisTemplate.setKeySerializer(new StringRedisSerializer());
        redisTemplate.setValueSerializer(new StringRedisSerializer());
        Long increment = operations.increment(TestConstant.COUNTER + key, 1);
        if(increment == 1){
            //设置1分钟过期
            redisTemplate.expire(TestConstant.COUNTER + key, 1, TimeUnit.MILLISECONDS);
        }
        if (increment > 100) {
            logger.info("用户IP[" + ip + "]访问地址[" + url + "]超过了限定的次数[" + 100 + "]");
            throw new RuntimeException("超出访问次数限制");
        }
        //GET  key
        return redisTemplate.opsForValue().get(TestConstant.COUNTER + key);
    }




}
